home *** CD-ROM | disk | FTP | other *** search
/ BCI NET / BCI NET Dec 94.iso / archives / applications / misc / pcal-4.5.lha / pcal / readfile.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-10-16  |  37.7 KB  |  1,763 lines

  1. /*
  2.  * readfile.c - Pcal routines concerned with reading and parsing the datefile
  3.  *
  4.  * Contents:
  5.  *
  6.  *        cleanup
  7.  *        clear_syms
  8.  *        date_type
  9.  *        do_define
  10.  *        do_ifdef
  11.  *        do_ifndef
  12.  *        do_include
  13.  *        do_undef
  14.  *        enter_day_info
  15.  *        find_easter
  16.  *        find_sym
  17.  *        find_year
  18.  *        get_holiday
  19.  *        get_keywd
  20.  *        get_month
  21.  *        get_ordinal
  22.  *        get_phase
  23.  *        get_prep
  24.  *        get_token
  25.  *        get_weekday
  26.  *        is_anyday
  27.  *        is_fquarter
  28.  *        is_fullmoon
  29.  *        is_holiday
  30.  *        is_lquarter
  31.  *        is_newmoon
  32.  *        is_weekday
  33.  *        is_workday
  34.  *        not_holiday
  35.  *        not_weekday
  36.  *        not_workday
  37.  *        parse
  38.  *        parse_date
  39.  *        parse_ord
  40.  *        parse_rel
  41.  *        read_datefile
  42.  *
  43.  * Revision history:
  44.  *
  45.  *    4.5    AWR    11/08/93    accept European dates of form "dd. mm."
  46.  *                    and "dd. mon"
  47.  *
  48.  *            04/28/93    restructure function definitions so
  49.  *                    function name appears in first column
  50.  *                    (to facilitate searching for definition
  51.  *                    by name)
  52.  *
  53.  *            03/05/93    Propagate null text to output
  54.  *
  55.  *            02/11/92    Support predefined holidays (strings
  56.  *                    and dispatch functions - cf. pcallang.h)
  57.  *
  58.  *    4.4    AWR    01/20/92    Use predominant color ("logical black")
  59.  *                    in is_weekday()
  60.  *
  61.  *            12/16/91    Avoid invalid array access in
  62.  *                    read_datefile()
  63.  *
  64.  *    4.3    AWR    10/25/91    Support moon phase wildcards and
  65.  *                    -Z flag (debug information)
  66.  *
  67.  *    4.2    AWR    10/03/91    Support "note/<n>" (user-selected
  68.  *                    notes box) as per Geoff Kuenning
  69.  *
  70.  *            09/30/91    Support "elif" in datefile
  71.  *
  72.  *    4.11    AWR    08/20/91    Support "nearest" keyword as per
  73.  *                    Andy Fyfe
  74.  *
  75.  *    4.0    AWR    02/19/91    Support negative ordinals
  76.  *
  77.  *            02/06/91    Support expressions in "if{n}def"
  78.  *
  79.  *            02/04/91    Support "even" and "odd" ordinals
  80.  *                    and ordinals > 5th; support "year"
  81.  *
  82.  *            01/15/91    Extracted from pcal.c
  83.  *
  84.  */
  85.  
  86. /*
  87.  * Standard headers:
  88.  */
  89.  
  90. #include <stdio.h>
  91. #include <string.h>
  92. #include <ctype.h>
  93.  
  94. /*
  95.  * Pcal-specific definitions:
  96.  */
  97.  
  98. #include "pcaldefs.h"
  99. #include "pcalglob.h"
  100. #include "pcallang.h"
  101.  
  102. /*
  103.  * Macros:
  104.  */
  105.  
  106. /* status codes returned by parse(), enter_day_info() */
  107. #define PARSE_OK    0    /* successful date parse */
  108. #define PARSE_INVDATE    1    /* nonexistent date */
  109. #define PARSE_INVLINE    2    /* syntax error */
  110. #define PARSE_NOMATCH    3    /* no match for wildcard */
  111.  
  112. /* codes for states in read_datefile() */
  113. #define PROCESSING    0    /* currently processing datefile lines */
  114. #define AWAITING_TRUE    1    /* awaiting first TRUE branch in "if{n}def" */
  115. #define SKIP_TO_ENDIF    2    /* finished processing first TRUE branch */
  116.  
  117. #define BLANK_TEXT    " "    /* substitute for null input text */
  118.  
  119. /* append date to list; terminate list */
  120. #define ADD_DATE(_m, _d, _y)    if (1) { \
  121.                 if (DEBUG(DEBUG_DATES)) \
  122.                     FPR(stderr, "matched %d/%d/%d\n", \
  123.                         _m, _d, _y); \
  124.                 pdate->mm = _m, pdate->dd = _d, pdate++->yy = _y; \
  125.                 } else
  126.  
  127. #define TERM_DATES        pdate->mm = pdate->dd = pdate->yy = 0
  128.  
  129. /*
  130.  * Globals:
  131.  */
  132.  
  133. static DATE dates[MAX_DATES+1];        /* array of date structures */
  134. static char *pp_sym[MAX_PP_SYMS];    /* preprocessor defined symbols */
  135.  
  136.  
  137. /*
  138.  * read_datefile - read and parse date file, handling preprocessor lines
  139.  *
  140.  * This is the main routine of this module.  It calls getline() to read each
  141.  * non-null line (stripped of leading blanks and trailing comments), loadwords()
  142.  * to "tokenize" it, and get_token() to classify it as a preprocessor directive
  143.  * or "other".  A switch{} statement takes the appropriate action for each
  144.  * token type; "other" lines are further classified by parse() (q.v.) which
  145.  * calls parse_date() (q.v.) to parse date entries and enter them in the data
  146.  * structure (as described in pcaldefs.h).
  147.  *
  148.  */
  149. void
  150. #ifdef PROTOS
  151. read_datefile(FILE *fp,        /* file pointer (assumed open) */
  152.           char *filename)    /* file name (for error messages) */
  153. #else
  154. read_datefile(fp, filename)
  155.     FILE *fp;        /* file pointer (assumed open) */
  156.     char *filename;        /* file name (for error messages) */
  157. #endif
  158. {
  159.     static int file_level = 0;
  160.     int if_level = 0;
  161.     int line = 0;
  162.  
  163.     int pptype, extra, ntokens, save_year, expr;
  164.     int (*pfcn)();
  165.     char *ptok;
  166.     char **pword;
  167.     char msg[STRSIZ], incpath[STRSIZ];
  168.  
  169.     /* stack for processing nested "if{n}defs" - required for "elif" */
  170.     struct {
  171.         int state;    /* PROCESSING, AWAITING_TRUE, SKIP_TO_ENDIF */
  172.         int else_ok;    /* is "elif" or "else" legal at this point? */
  173.     } if_state[MAX_IF_NESTING+1];
  174.  
  175.     if (fp == NULL)                /* whoops, no date file */
  176.         return;
  177.  
  178.     /* note that there is no functional limit on file nesting; this is
  179.      * mostly to catch infinite loops (e.g., a includes b, b includes a)
  180.      */
  181.     if (++file_level > MAX_FILE_NESTING) {
  182.         ERR(E_FILE_NESTING);
  183.         exit(EXIT_FAILURE);
  184.     }
  185.  
  186.     save_year = curr_year;            /* save default year */
  187.  
  188.     if_state[0].state = PROCESSING;        /* set up initial state */
  189.     if_state[0].else_ok = FALSE;
  190.  
  191.     /* read lines until EOF */
  192.  
  193.     while (getline(fp, lbuf, &line)) {
  194.         char suffix;
  195.  
  196.         if (DEBUG(DEBUG_PP)) {
  197.             FPR(stderr, "%s (%d)", filename, line);
  198.             if (if_state[if_level].state == PROCESSING)
  199.                 FPR(stderr, ": '%s'", lbuf);
  200.             FPR(stderr, "\n");
  201.         }
  202.  
  203.         ntokens = loadwords(words, lbuf); /* split line into tokens */
  204.         pword = words;            /* point to the first */
  205.  
  206.         /* get token type and pointers to function and name */
  207.  
  208.         suffix = words[0][strlen(words[0])-1];
  209.         pptype = get_token(*pword++);
  210.         pfcn = pptype == PP_OTHER ? NULL : pp_info[pptype].pfcn;
  211.         ptok = pptype == PP_OTHER ? "" : pp_info[pptype].name;
  212.  
  213.         switch (pptype) {
  214.  
  215.         case PP_DEFINE:
  216.         case PP_UNDEF:
  217.             if (if_state[if_level].state == PROCESSING)
  218.                 (void) (*pfcn)(*pword);
  219.             extra = ntokens > 2;
  220.             break;
  221.  
  222.         case PP_ELIF:
  223.             if (!if_state[if_level].else_ok) {
  224.                 ERR(E_ELIF_ERR);
  225.                 break;
  226.             }
  227.  
  228.             /* if a true expression has just been processed, disable
  229.              * processing and skip to endif; if no true expression
  230.              * has been found yet and the current expression is
  231.              * true, enable processing
  232.              */
  233.             switch (if_state[if_level].state) {
  234.             case PROCESSING:
  235.                 if_state[if_level].state = SKIP_TO_ENDIF;
  236.                 break;
  237.             case AWAITING_TRUE:
  238.                 copy_text(lbuf, pword);    /* reconstruct string */
  239.                 if ((expr = (*pfcn)(lbuf)) == EXPR_ERR) {
  240.                     ERR(E_EXPR_SYNTAX);
  241.                     expr = FALSE;
  242.                 }
  243.                 if (expr)
  244.                     if_state[if_level].state = PROCESSING;
  245.                 break;
  246.             }
  247.  
  248.             extra = FALSE;
  249.             break;
  250.  
  251.         case PP_ELSE:
  252.             if (!if_state[if_level].else_ok) {
  253.                 ERR(E_ELSE_ERR);
  254.                 break;
  255.             }
  256.  
  257.             /* if a true condition has just been processed, disable
  258.              * processing and skip to endif; if no true condition
  259.              * has been found yet, enable processing
  260.              */
  261.             switch (if_state[if_level].state) {
  262.             case PROCESSING:
  263.                 if_state[if_level].state = SKIP_TO_ENDIF;
  264.                 break;
  265.             case AWAITING_TRUE:
  266.                 if_state[if_level].state = PROCESSING;
  267.                 break;
  268.             }
  269.  
  270.             /* subsequent "elif" or "else" forbidden */
  271.             if_state[if_level].else_ok = FALSE;
  272.             extra = ntokens > 1;
  273.             break;
  274.  
  275.         case PP_ENDIF:
  276.             if (if_level < 1) {
  277.                 ERR(E_END_ERR);
  278.                 break;
  279.             }
  280.             if_level--;
  281.             extra = ntokens > 1;
  282.             break;
  283.  
  284.         case PP_IFDEF:
  285.         case PP_IFNDEF:
  286.             /* "if{n}def"s nested too deeply? */
  287.             if (++if_level > MAX_IF_NESTING) {
  288.                 ERR(E_IF_NESTING);
  289.                 exit(EXIT_FAILURE);
  290.                 break;
  291.             }
  292.  
  293.             /* if processing enabled at outer level, evaluate
  294.              * expression and enable/disable processing for
  295.              * following clause; if not, skip to matching endif
  296.              */
  297.             if (if_state[if_level-1].state == PROCESSING) {
  298.                 copy_text(lbuf, pword);    /* reconstruct string */
  299.                 if ((expr = (*pfcn)(lbuf)) == EXPR_ERR) {
  300.                     ERR(E_EXPR_SYNTAX);
  301.                     expr = FALSE;
  302.                 }
  303.                 if_state[if_level].state = expr ? PROCESSING :
  304.                                   AWAITING_TRUE;
  305.             } else
  306.                 if_state[if_level].state = SKIP_TO_ENDIF;
  307.  
  308.             if_state[if_level].else_ok = TRUE;
  309.             extra = FALSE;
  310.             break;
  311.  
  312.         case PP_INCLUDE:
  313.             if (if_state[if_level].state == PROCESSING)
  314.                 do_include(mk_path(incpath, filename), *pword,
  315.                        suffix == '?');
  316.             extra = ntokens > 2;
  317.             break;
  318.  
  319.         case PP_OTHER:    /* none of the above - parse as date */
  320.             if (if_state[if_level].state == PROCESSING) {
  321.                 switch (parse(words, filename)) {
  322.                 case PARSE_INVDATE:
  323.                     ERR(E_INV_DATE);
  324.                     break;
  325.  
  326.                 case PARSE_INVLINE:
  327.                     ERR(E_INV_LINE);
  328.                     break;
  329.  
  330.                 case PARSE_NOMATCH:
  331.                     ERR(E_NO_MATCH);
  332.                     break;
  333.                 }
  334.             }
  335.             extra = FALSE;
  336.             break;
  337.  
  338.         } /* end switch */
  339.  
  340.         if (extra) {        /* extraneous data? */
  341.             sprintf(msg, E_GARBAGE, ptok);
  342.             ERR(msg);
  343.         }
  344.  
  345.     } /* end while */
  346.  
  347.     if (if_level > 0)
  348.         FPR(stderr, E_UNT_IFDEF, progname, filename);
  349.  
  350.     file_level--;
  351.     curr_year = save_year;        /* restore default year */
  352. }
  353.  
  354.  
  355. /*
  356.  * Routines to free allocated data (symbol table and data structure) 
  357.  */
  358.  
  359.  
  360. /*
  361.  * clear_syms - clear and deallocate the symbol table
  362.  */
  363. void
  364. #ifdef PROTOS
  365. clear_syms(void)
  366. #else
  367. clear_syms()
  368. #endif
  369. {
  370.     int i;
  371.  
  372.     for (i = 0; i < MAX_PP_SYMS; i++)
  373.         if (pp_sym[i]) {
  374.             free(pp_sym[i]);
  375.             pp_sym[i] = NULL;
  376.         }
  377. }
  378.  
  379.  
  380. /*
  381.  * cleanup - free all allocated data
  382.  */
  383. void
  384. #ifdef PROTOS
  385. cleanup(void)
  386. #else
  387. cleanup()
  388. #endif
  389. {
  390.     int i, j;
  391.     year_info *py, *pny;
  392.     month_info *pm;
  393.     day_info *pd, *pnd;
  394.  
  395.     for (py = head; py; py = pny) {        /* main data structure */
  396.         pny = py->next;
  397.         for (i = 0; i < 12; i++) {
  398.             if ((pm = py->month[i]) == NULL)
  399.                 continue;
  400.             for (j = 0; j < LAST_NOTE_DAY; j++)
  401.                 for (pd = pm->day[j]; pd; pd = pnd) {
  402.                     pnd = pd->next;
  403.                     free(pd->text);
  404.                     free(pd);
  405.                 }
  406.             free(pm);
  407.         }
  408.     free(py);
  409.     }
  410.  
  411.     clear_syms();                /* symbol table */
  412.  
  413. }
  414.  
  415.  
  416. /*
  417.  * Preprocessor token and symbol table routines
  418.  */
  419.  
  420.  
  421. /*
  422.  * find_sym - look up symbol; return symbol table index if found, PP_SYM_UNDEF
  423.  * if not found
  424.  */
  425. int
  426. #ifdef PROTOS
  427. find_sym(char *sym)
  428. #else
  429. find_sym(sym)
  430.     char *sym;
  431. #endif
  432. {
  433.     int i;
  434.  
  435.     if (!sym)
  436.         return PP_SYM_UNDEF;
  437.  
  438.     for (i = 0; i < MAX_PP_SYMS; i++)
  439.         if (pp_sym[i] && ci_strcmp(pp_sym[i], sym) == 0)
  440.             return i;
  441.  
  442.     return PP_SYM_UNDEF;
  443. }
  444.  
  445.  
  446. /*
  447.  * do_ifdef - return TRUE if 'expr' is true; FALSE if not; EXPR_ERR if invalid
  448.  */
  449. int
  450. #ifdef PROTOS
  451. do_ifdef(char *expr)
  452. #else
  453. do_ifdef(expr)
  454.     char *expr;
  455. #endif
  456. {
  457.     return parse_expr(expr);
  458. }
  459.  
  460.  
  461. /*
  462.  * do_ifndef - return FALSE if 'expr' is true; TRUE if not; EXPR_ERR if invalid
  463.  */
  464. int
  465. #ifdef PROTOS
  466. do_ifndef(char *expr)
  467. #else
  468. do_ifndef(expr)
  469.     char *expr;
  470. #endif
  471. {
  472.     int val;
  473.  
  474.     return (val = parse_expr(expr)) == EXPR_ERR ? EXPR_ERR : ! val;
  475. }
  476.  
  477.  
  478. /*
  479.  * do_define - enter 'sym' into symbol table; if 'sym' NULL, clear symbol table.
  480.  * Always returns 0 (for compatibility with other dispatch functions).
  481.  */
  482. int
  483. #ifdef PROTOS
  484. do_define(char *sym)
  485. #else
  486. do_define(sym)
  487.     char *sym;
  488. #endif
  489. {
  490.     int i;
  491.  
  492.     if (! sym) {        /* null argument - clear all definitions */
  493.         clear_syms();
  494.         return 0;
  495.     }
  496.  
  497.     if (do_ifdef(sym))    /* already defined? */
  498.         return 0;
  499.  
  500.     for (i = 0; i < MAX_PP_SYMS; i++)    /* find room for it */
  501.         if (! pp_sym[i]) {
  502.             strcpy(pp_sym[i] = alloc(strlen(sym)+1), sym);
  503.             return 0;
  504.         }
  505.  
  506.     FPR(stderr, E_SYMFULL, progname, sym);
  507.     return 0;
  508. }
  509.  
  510.  
  511. /*
  512.  * do_undef - undefine 'sym' and free its space; no error if not defined.
  513.  * Always return 0 (for compatibility with other dispatch fcns).
  514.  */
  515. int
  516. #ifdef PROTOS
  517. do_undef(char *sym)
  518. #else
  519. do_undef(sym)
  520.     char *sym;
  521. #endif
  522. {
  523.     int i;
  524.  
  525.     if (! sym) 
  526.         return 0;
  527.  
  528.     if ((i = find_sym(sym)) != PP_SYM_UNDEF) {
  529.         free(pp_sym[i]);
  530.         pp_sym[i] = NULL;
  531.     }
  532.  
  533.     return 0;
  534. }
  535.  
  536.  
  537. /*
  538.  * do_include - include specified file (optionally in "" or <>); always
  539.  * returns 0 (for compatibility with related functions returning int)
  540.  */
  541. int
  542. #ifdef PROTOS
  543. do_include(char *path,
  544.        char *name,
  545.        int noerr)
  546. #else
  547. do_include(path, name, noerr)
  548.     char *path;        /* path to file */
  549.     char *name;        /* file name */
  550.     int  noerr;        /* ignore nonexistent file */
  551. #endif
  552. {
  553.     FILE *fp;
  554.     char *p, incfile[STRSIZ], tmpnam[STRSIZ];
  555.  
  556.     if (! name)        /* whoops, no date file */
  557.         return 0;
  558.  
  559.     /* copy name, stripping "" or <> */
  560.     strcpy(tmpnam, name + (*name == '"' || *name == '<'));
  561.     if ((p = P_LASTCHAR(tmpnam)) && *p == '"' || *p == '>')
  562.         *p = '\0';
  563.  
  564.     /* special hack - replace %y with last two digits of curr_year */
  565.     while ((p = strchr(tmpnam, '%')) != NULL && *(p+1) == 'y') {
  566.         *p++ = (curr_year / 10) % 10 + '0';
  567.         *p = curr_year % 10 + '0';
  568.     }
  569.  
  570.     if ((fp = fopen(mk_filespec(incfile, path, tmpnam), "r")) == NULL) {
  571.         if (noerr)
  572.             return 0;
  573.         FPR(stderr, E_FOPEN_ERR, progname, incfile);
  574.         exit(EXIT_FAILURE);
  575.     }
  576.  
  577.     read_datefile(fp, incfile);    /* recursive call */
  578.     fclose(fp);
  579.  
  580.     return 0;
  581. }
  582.  
  583.  
  584.  
  585. /*
  586.  * Dispatch functions for wildcard matching
  587.  */
  588.  
  589.  
  590. /*
  591.  * is_anyday - dummy function which always returns TRUE
  592.  */
  593. int
  594. #ifdef PROTOS
  595. is_anyday(int mm,
  596.       int dd,
  597.       int yy)
  598. #else
  599. is_anyday(mm, dd, yy)
  600.     int mm;
  601.     int dd;
  602.     int yy;
  603. #endif
  604. {
  605.     return TRUE;
  606. }
  607.  
  608.  
  609. /*
  610.  * is_weekday - determine whether or not mm/dd/yy is a weekday (i.e., the
  611.  * day of the week normally prints in "logical black", the most prevalent
  612.  * color)
  613.  */
  614. int
  615. #ifdef PROTOS
  616. is_weekday(int mm,
  617.        int dd,
  618.        int yy)
  619. #else
  620. is_weekday(mm, dd, yy)
  621.     int mm;
  622.     int dd;
  623.     int yy;
  624. #endif
  625. {
  626.     return day_color[calc_weekday(mm, dd, yy)] == weekday_color;
  627. }
  628.  
  629.  
  630. /*
  631.  * is_workday - determine whether or not mm/dd/yy is a workday (i.e., the
  632.  * day of the week normally prints in black and the date is not a holiday)
  633.  */
  634. int
  635. #ifdef PROTOS
  636. is_workday(int mm,
  637.        int dd,
  638.        int yy)
  639. #else
  640. is_workday(mm, dd, yy)
  641.     int mm;
  642.     int dd;
  643.     int yy;
  644. #endif
  645. {
  646.     return is_weekday(mm, dd, yy) && ! is_holiday(mm, dd, yy);
  647. }
  648.  
  649.  
  650. /*
  651.  * is_holiday - determine whether or not mm/dd/yy is a holiday
  652.  */
  653. int
  654. #ifdef PROTOS
  655. is_holiday(int mm,
  656.        int dd,
  657.        int yy)
  658. #else
  659. is_holiday(mm, dd, yy)
  660.     int mm;
  661.     int dd;
  662.     int yy;
  663. #endif
  664. {
  665.     year_info *py;
  666.     month_info *pm;
  667.  
  668.     pm = (py = find_year(yy, FALSE)) ? py->month[mm-1] : NULL;
  669.     return pm ? (pm->holidays & (1L << (dd-1))) != 0 : FALSE;
  670. }
  671.  
  672.  
  673. /*
  674.  * not_XXXXX - converses of is_XXXXX above
  675.  */
  676. int
  677. #ifdef PROTOS
  678. not_weekday(int mm,
  679.         int dd,
  680.         int yy)
  681. #else
  682. not_weekday(mm, dd, yy)
  683.     int mm;
  684.     int dd;
  685.     int yy;
  686. #endif
  687. {
  688.     return !is_weekday(mm, dd, yy);
  689. }
  690.  
  691.  
  692. int
  693. #ifdef PROTOS
  694. not_workday(int mm,
  695.         int dd,
  696.         int yy)
  697. #else
  698. not_workday(mm, dd, yy)
  699.     int mm;
  700.     int dd;
  701.     int yy;
  702. #endif
  703. {
  704.     return !is_workday(mm, dd, yy);
  705. }
  706.  
  707.  
  708. int
  709. #ifdef PROTOS
  710. not_holiday(int mm,
  711.         int dd,
  712.         int yy)
  713. #else
  714. not_holiday(mm, dd, yy)
  715.     int mm;
  716.     int dd;
  717.     int yy;
  718. #endif
  719. {
  720.     return !is_holiday(mm, dd, yy);
  721. }
  722.  
  723.  
  724. /*
  725.  * is_newmoon - determine whether or not mm/dd/yy is the date of a new moon
  726.  */
  727. int
  728. #ifdef PROTOS
  729. is_newmoon(int mm,
  730.        int dd,
  731.        int yy)
  732. #else
  733. is_newmoon(mm, dd, yy)
  734.     int mm;
  735.     int dd;
  736.     int yy;
  737. #endif
  738. {
  739.     int quarter;
  740.  
  741.     (void) find_phase(mm, dd, yy, &quarter);
  742.     return quarter == MOON_NM;
  743. }
  744.  
  745.  
  746. /*
  747.  * is_firstq - determine whether or not mm/dd/yy is the date of a first quarter
  748.  */
  749. int
  750. #ifdef PROTOS
  751. is_firstq(int mm,
  752.       int dd,
  753.       int yy)
  754. #else
  755. is_firstq(mm, dd, yy)
  756.     int mm;
  757.     int dd;
  758.     int yy;
  759. #endif
  760. {
  761.     int quarter;
  762.  
  763.     (void) find_phase(mm, dd, yy, &quarter);
  764.     return quarter == MOON_1Q;
  765. }
  766.  
  767.  
  768. /*
  769.  * is_fullmoon - determine whether or not mm/dd/yy is the date of a full moon
  770.  */
  771. int
  772. #ifdef PROTOS
  773. is_fullmoon(int mm,
  774.         int dd,
  775.         int yy)
  776. #else
  777. is_fullmoon(mm, dd, yy)
  778.     int mm;
  779.     int dd;
  780.     int yy;
  781. #endif
  782. {
  783.     int quarter;
  784.  
  785.     (void) find_phase(mm, dd, yy, &quarter);
  786.     return quarter == MOON_FM;
  787. }
  788.  
  789.  
  790. /*
  791.  * is_lastq - determine whether or not mm/dd/yy is the date of a last quarter
  792.  */
  793. int
  794. #ifdef PROTOS
  795. is_lastq(int mm,
  796.      int dd,
  797.      int yy)
  798. #else
  799. is_lastq(mm, dd, yy)
  800.     int mm;
  801.     int dd;
  802.     int yy;
  803. #endif
  804. {
  805.     int quarter;
  806.  
  807.     (void) find_phase(mm, dd, yy, &quarter);
  808.     return quarter == MOON_3Q;
  809. }
  810.  
  811.  
  812.  
  813. /*
  814.  * Routines to find predefined holidays too complicated to express as Pcal
  815.  * date strings.  All add the matching date(s) (yes, holidays which span
  816.  * multiple days are allowed) to the dates[] array (pointed to by pdate)
  817.  * and return the number of matching dates.
  818.  */ 
  819.  
  820.  
  821. /*
  822.  * find_easter - find Easter of current year; add to date array (adapted
  823.  * by parties unknown from Knuth's _The Art of Computer Programming_, v. 1)
  824.  */
  825.  
  826. #define METONIC 19            /* length of metonic cycle */
  827.  
  828. int
  829. #ifdef PROTOS
  830. find_easter(DATE *pdate)        /* pointer into date array */
  831. #else
  832. find_easter(pdate)
  833.     DATE *pdate;            /* pointer into date array */
  834. #endif
  835. {
  836.     /* Easter is defined as the Sunday after the full moon on or
  837.      * after March 21.  You could express that as "1st Sun after
  838.      * 1st full_moon ooa 21st day in March" instead of using this
  839.      * routine, but depending on your timezone your full moon might
  840.      * not fall on the same day as the "official" one.
  841.      */
  842.  
  843.     DATE *sv_pdate = pdate;
  844.     register int epact, fm;
  845.     int golden, century, nleap;
  846.  
  847.     golden = curr_year % METONIC + 1;
  848.     century = curr_year / 100 + 1;
  849.     nleap = 3 * century / 4 - 12;
  850.  
  851.     /* correct for moon's orbit */
  852.     epact = (11 * golden + 20 + (8 * century + 5) / 25 - 5 - nleap) % 30;
  853.     if (epact < 0)
  854.             epact += 30;
  855.     if (epact == 25 && golden > 11 || epact == 24)
  856.             ++epact;
  857.  
  858.     /* find full moon on or after 3/21 */
  859.     fm = 44 - epact;
  860.     if (fm < 21)
  861.             fm += 30;
  862.  
  863.     /* find date of following Sunday */
  864.     fm += 7 - ((int)(5L * curr_year / 4) - nleap - 10 + fm) % 7;
  865.  
  866.     /* add Easter to date array (adjust if in April) */
  867.     ADD_DATE(fm <= 31 ? MAR : APR, fm <= 31 ? fm : fm - 31, curr_year);
  868.  
  869.     /* return number of dates added (always 1 in this routine) */
  870.     return pdate - sv_pdate;
  871. }
  872.  
  873. /*
  874.  * Keyword classification routines
  875.  */
  876.  
  877. /*
  878.  * get_month - convert alpha (or, optionally, numeric) string to month; return 
  879.  * 1..12 if valid, NOT_MONTH if not, ALL_MONTHS if "all", ENTIRE_YEAR if "year"
  880.  */
  881. int
  882. #ifdef PROTOS
  883. get_month(char *cp,        /* string to convert */
  884.       int numeric_ok,    /* accept numeric string ? */
  885.       int year_ok)        /* accept "year"? */
  886. #else
  887. get_month(cp, numeric_ok, year_ok)
  888.     char *cp;        /* string to convert */
  889.     int numeric_ok;        /* accept numeric string ? */
  890.     int year_ok;        /* accept "year"? */
  891. #endif
  892. {
  893.     int mm;
  894.  
  895.     if (! cp)
  896.         return NOT_MONTH;
  897.  
  898.     if (get_keywd(cp) == DT_ALL)
  899.         return ALL_MONTHS;
  900.  
  901.     if (year_ok && get_keywd(cp) == DT_YEAR)
  902.         return ENTIRE_YEAR;
  903.  
  904.     if (numeric_ok && isdigit(*cp))
  905.         mm = atoi(cp);
  906.     else
  907.         for (mm = JAN;
  908.              mm <= DEC && ci_strncmp(cp, months[mm-1], MIN_MONTH_LEN);
  909.              mm++)
  910.             ;
  911.  
  912.     return mm >= JAN && mm <= DEC ? mm : NOT_MONTH;
  913. }
  914.  
  915.  
  916. /*
  917.  * get_weekday - look up string in weekday list; return 0-6 if valid,
  918.  * NOT_WEEKDAY if not.  If wild_ok flag is set, accept "day", "weekday",
  919.  * "workday", "holiday", or moon phase and return appropriate value.
  920.  */
  921. int
  922. #ifdef PROTOS
  923. get_weekday(char *cp,
  924.         int wild_ok)
  925. #else
  926. get_weekday(cp, wild_ok)
  927.     char *cp;
  928.     int wild_ok;
  929. #endif
  930. {
  931.     int w;
  932.  
  933.     if (!cp)
  934.         return NOT_WEEKDAY;
  935.  
  936.     if (wild_ok) {        /* try wildcards first */
  937.         for (w = WILD_FIRST_WKD; w <= WILD_LAST_WKD; w++)
  938.             if (ci_strncmp(cp, days[w], strlen(days[w])) == 0)
  939.                 return w;
  940.         if ((w = get_phase(cp)) != MOON_OTHER)
  941.             return w + WILD_FIRST_MOON;
  942.     }
  943.  
  944.     for (w = SUN; w <= SAT; w++)
  945.         if (ci_strncmp(cp, days[w], MIN_DAY_LEN) == 0)
  946.             return w;
  947.  
  948.     return NOT_WEEKDAY;
  949. }
  950.  
  951.  
  952. /*
  953.  * get_keywd - look up string in misc. keyword list; return keyword code
  954.  * if valid, DT_OTHER if not
  955.  */
  956. int
  957. #ifdef PROTOS
  958. get_keywd(char *cp)
  959. #else
  960. get_keywd(cp)
  961.     char *cp;
  962. #endif
  963. {
  964.     KWD *k;
  965.  
  966.     if (!cp)
  967.         return DT_OTHER;
  968.  
  969.     for (k = keywds;
  970.          k->name && ci_strncmp(cp, k->name, strlen(k->name));
  971.          k++)
  972.         ;
  973.  
  974.     return k->code;
  975. }
  976.  
  977.  
  978. /*
  979.  * get_ordinal - look up string in ordinal list; return ordinal code (and
  980.  * fill in ordinal value) if valid, return ORD_OTHER if not
  981.  */
  982. int
  983. #ifdef PROTOS
  984. get_ordinal(char *cp,
  985.         int *pval)
  986. #else
  987. get_ordinal(cp, pval)
  988.     char *cp;
  989.     int *pval;
  990. #endif
  991. {
  992.     KWD_O *o;
  993.     int val;
  994.     char **psuf;
  995.  
  996.     if (!cp)
  997.         return ORD_OTHER;
  998.  
  999.     if (isdigit(*cp) || *cp == '-') {        /* numeric? */
  1000.         if ((val = atoi(cp)) == 0)
  1001.             return ORD_OTHER;
  1002.  
  1003.         if (*cp == '-')                /* skip over number */
  1004.             cp++;
  1005.         cp += strspn(cp, DIGITS);
  1006.  
  1007.         for (psuf = ord_suffix; *psuf; psuf++)    /* find suffix */
  1008.             if (ci_strcmp(cp, *psuf) == 0) {
  1009.                 *pval = val;
  1010.                 return val < 0 ? ORD_NEGNUM : ORD_POSNUM;
  1011.             }
  1012.  
  1013.         return ORD_OTHER;
  1014.     }
  1015.  
  1016.     /* look for word in ordinals list */
  1017.  
  1018.     for (o = ordinals; o->name && ci_strncmp(cp, o->name, MIN_ORD_LEN); o++)
  1019.         ;
  1020.  
  1021.     *pval = o->value;
  1022.     return o->code;
  1023. }
  1024.  
  1025.  
  1026. /*
  1027.  * get_phase - convert moon phase string to appropriate value
  1028.  */
  1029. int
  1030. #ifdef PROTOS
  1031. get_phase(char *cp)
  1032. #else
  1033. get_phase(cp)
  1034.     char *cp;
  1035. #endif
  1036. {
  1037.     KWD *p;
  1038.  
  1039.     if (!cp)
  1040.         return MOON_OTHER;
  1041.  
  1042.     for (p = phases; p->name && ci_strcmp(cp, p->name); p++)
  1043.         ;
  1044.  
  1045.     return p->code;
  1046. }
  1047.  
  1048.  
  1049. /*
  1050.  * get_prep - look up string in preposition list; return preposition code if 
  1051.  * valid, PR_OTHER if not
  1052.  */
  1053. int
  1054. #ifdef PROTOS
  1055. get_prep(char *cp)
  1056. #else
  1057. get_prep(cp)
  1058.     char *cp;
  1059. #endif
  1060. {
  1061.     KWD *p;
  1062.  
  1063.     if (!cp)
  1064.         return PR_OTHER;
  1065.  
  1066.     for (p = preps; p->name && ci_strncmp(cp, p->name, MIN_PREP_LEN); p++)
  1067.         ;
  1068.  
  1069.     return p->code;
  1070. }
  1071.  
  1072.  
  1073. /*
  1074.  * get_token - look up 'token' in list of preprocessor tokens; return its
  1075.  * code if found, PP_OTHER if not
  1076.  */
  1077. int
  1078. #ifdef PROTOS
  1079. get_token(char *token)
  1080. #else
  1081. get_token(token)
  1082.     char *token;
  1083. #endif
  1084. {
  1085.     KWD_F *p;
  1086.  
  1087.     for (p = pp_info;
  1088.              p->name && ci_strncmp(token, p->name, MIN_PPTOK_LEN);
  1089.          p++)
  1090.         ;
  1091.  
  1092.     return p->code;
  1093. }
  1094.  
  1095.  
  1096. /*
  1097.  * get_holiday - look up string in holiday list; return its index if found,
  1098.  * NOT_HOLIDAY if not
  1099.  */
  1100. int
  1101. #ifdef PROTOS
  1102. get_holiday(char *cp)
  1103. #else
  1104. get_holiday(cp)
  1105.     char *cp;
  1106. #endif
  1107. {
  1108.     KWD_H *p;
  1109.     char tmp[STRSIZ];
  1110.  
  1111.     strcpy(tmp, cp);        /* make copy without any trailing '*' */
  1112.     if (LASTCHAR(tmp) == '*')
  1113.         LASTCHAR(tmp) = '\0';
  1114.  
  1115.     for (p = holidays; p->name; p++)
  1116.         if (ci_strcmp(tmp, p->name) == 0)
  1117.             return p - holidays;
  1118.  
  1119.     return NOT_HOLIDAY;
  1120. }
  1121.  
  1122.  
  1123. /*
  1124.  * date_type - examine token and return date type code; if DT_MONTH, DT_ORDINAL,
  1125.  * or DT_WEEKDAY, fill in appropriate code (and value if DT_ORDINAL)
  1126.  */
  1127. int
  1128. #ifdef PROTOS
  1129. date_type(char *cp,    /* pointer to start of token  */
  1130.       int *pn,    /* token type code (returned) */
  1131.       int *pv)    /* ordinal value (returned)   */
  1132. #else
  1133. date_type(cp, pn, pv)
  1134.     char *cp;    /* pointer to start of token  */
  1135.     int *pn;    /* token type code (returned) */
  1136.     int *pv;    /* ordinal value (returned)   */
  1137. #endif
  1138. {
  1139.     int n, v;
  1140.  
  1141.     /* look for weekdays first, to catch wildcards "1q", "3q", etc. */
  1142.     if ((n = get_weekday(cp, TRUE)) != NOT_WEEKDAY)    /* weekday name? */
  1143.         return (*pn = n, DT_WEEKDAY);
  1144.  
  1145.     if ((n = get_holiday(cp)) != NOT_HOLIDAY)    /* holiday? */
  1146.         return (*pn = n, DT_HOLIDAY);
  1147.  
  1148.     if ((n = get_ordinal(cp, &v)) != ORD_OTHER)    /* ordinal? */
  1149.         return (*pn = n, *pv = v, DT_ORDINAL);
  1150.  
  1151.     if (isdigit(*cp))                /* other digit? */
  1152.         return (IS_NUMERIC(cp) ||
  1153.                 (date_style == EUR_DATES && IS_EURDATE(cp))) ?
  1154.             DT_EURDATE : DT_DATE;
  1155.  
  1156.     /* "all" can be either a keyword or a month wildcard - look for
  1157.        the former usage first */
  1158.  
  1159.     if ((n = get_keywd(cp)) != DT_OTHER)
  1160.         return n;
  1161.  
  1162.     if ((n = get_month(cp, FALSE, FALSE)) != NOT_MONTH)  /* month name? */
  1163.         return (*pn = n, DT_MONTH);
  1164.  
  1165.     return DT_OTHER;        /* unrecognized keyword - give up */
  1166.  
  1167. }
  1168.  
  1169.  
  1170.  
  1171. /*
  1172.  * Routines for entering data in the data structure (described in pcaldefs.h)
  1173.  */
  1174.  
  1175.  
  1176. /*
  1177.  * find_year - find record in year list; optionally create if not present 
  1178.  */
  1179. year_info *
  1180. #ifdef PROTOS
  1181. find_year(int year,
  1182.       int insert)            /* insert if missing */
  1183. #else
  1184. find_year(year, insert)
  1185.     int year;
  1186.     int insert;            /* insert if missing */
  1187. #endif
  1188. {
  1189.     year_info *pyear, *plast, *p;
  1190.  
  1191.     for (plast = NULL, pyear = head;        /* search linked list */
  1192.          pyear && pyear->year < year;
  1193.          plast = pyear, pyear = pyear->next)
  1194.         ;
  1195.  
  1196.     if (pyear && pyear->year == year)        /* found - return it */
  1197.         return pyear;
  1198.  
  1199.     if (insert) {        /* not found - insert it if requested */
  1200.         p = (year_info *) alloc((int) sizeof(year_info));    /* create new record */
  1201.         p->year = year;
  1202.  
  1203.         p->next = pyear;                /* link it in */
  1204.         return *(plast ? &plast->next : &head) = p;
  1205.     }
  1206.     else
  1207.         return NULL;
  1208. }
  1209.  
  1210.  
  1211. /*
  1212.  * enter_day_info - enter text for specified day; avoid entering duplicates.
  1213.  * Returns PARSE_INVDATE if date invalid, PARSE_OK if OK; if symbol FEB_29_OK
  1214.  * is non-zero (cf. pcaldefs.h), will silently ignore Feb 29 of common year.
  1215.  */
  1216. int
  1217. #ifdef PROTOS
  1218. enter_day_info(int m,
  1219.            int d,
  1220.            int y,
  1221.            int text_type,
  1222.            char **pword)
  1223. #else
  1224. enter_day_info(m, d, y, text_type, pword)
  1225.     int m, d, y;
  1226.     int text_type;
  1227.     char **pword;
  1228. #endif
  1229. {
  1230.     static year_info *pyear;
  1231.     static int prev_year = 0;
  1232.     month_info *pmonth;
  1233.     day_info *pday, *plast;
  1234.     int is_holiday = text_type == HOLIDAY_TEXT;
  1235.     char text[LINSIZ];
  1236.  
  1237.     if (! is_valid(m, d >= FIRST_NOTE_DAY && text_type == NOTE_TEXT ? 1 : d, y))
  1238.         return (m == FEB && d == 29 && FEB_29_OK) ? PARSE_OK : PARSE_INVDATE;
  1239.  
  1240.     if (y != prev_year)        /* avoid unnecessary year lookup */
  1241.         pyear = find_year(y, 1);
  1242.  
  1243.     --m, --d;            /* adjust for use as subscripts */
  1244.  
  1245.     if ((pmonth = pyear->month[m]) == NULL)    /* find/create month record */
  1246.         pyear->month[m] = pmonth = (month_info *) alloc((int) sizeof(month_info));
  1247.  
  1248.     if (is_holiday)
  1249.         pmonth->holidays |= (1L << d);
  1250.  
  1251.     /* insert text for day at end of list (preserving the order of entry
  1252.      * for multiple lines on same day); eliminate those differing only
  1253.      * in spacing and capitalization from existing entries
  1254.          */
  1255.  
  1256.     copy_text(text, pword);    /* consolidate text from lbuf into text */
  1257.  
  1258.     if (DEBUG(DEBUG_DATES)) {
  1259.         char *p;
  1260.         FPR(stderr, "%02d/%02d/%d%c '", m+1, d+1, y,
  1261.             is_holiday ? '*' : ' ');
  1262.         for (p = text; *p; p++)
  1263.             FPR(stderr, isprint(*p) ? "%c" : "\\%03o", *p & CHAR_MSK);
  1264.         FPR(stderr, "'\n");
  1265.     }
  1266.  
  1267. #if KEEP_NULL_LINES        /* preserve blank text lines in output */
  1268.     if (*text == '\0' && pmonth->day[d])
  1269.         strcpy(text, BLANK_TEXT);
  1270. #endif
  1271.  
  1272.     /* check that non-null text is unique */
  1273.  
  1274.     if (*text) {
  1275.         for (plast = NULL, pday = pmonth->day[d];
  1276.              pday;
  1277.              plast = pday, pday = pday->next)
  1278.             if (ci_strcmp(pday->text, text) == 0
  1279. #if KEEP_NULL_LINES
  1280.                 && strcmp(text, BLANK_TEXT) != 0
  1281. #endif
  1282.                 ) {
  1283.                 pday->is_holiday |= is_holiday;
  1284.                 return PARSE_OK;
  1285.                 }
  1286.  
  1287.         /* unique - add to end of list */
  1288.  
  1289.         pday = (day_info *) alloc(sizeof(day_info));
  1290.         pday->is_holiday = is_holiday;
  1291.         strcpy(pday->text = (char *) alloc(strlen(text)+1), text);
  1292.         pday->next = NULL;
  1293.         *(plast ? &plast->next : &pmonth->day[d]) = pday;
  1294.     }
  1295.  
  1296.     return PARSE_OK;
  1297. }
  1298.  
  1299.  
  1300.  
  1301. /*
  1302.  * Date parsing routines:
  1303.  */
  1304.  
  1305.  
  1306. /*
  1307.  * parse_ord - parse an ordinal date spec (e.g. "first Monday in September",
  1308.  * "every Sunday in October", "last workday in all"); return PARSE_OK if line
  1309.  * syntax valid, PARSE_INVLINE if not.  Write all matching dates (if any) to
  1310.  * global array dates[]; terminate date list with null entry.
  1311.  */
  1312. int
  1313. #ifdef PROTOS
  1314. parse_ord(int ord,        /* valid ordinal code - from get_ordinal() */
  1315.       int val,        /* ordinal value - also from get_ordinal() */
  1316.       char **pword)        /* pointer to word after ordinal */
  1317. #else
  1318. parse_ord(ord, val, pword)
  1319.     int ord;        /* valid ordinal code - from get_ordinal() */
  1320.     int val;        /* ordinal value - also from get_ordinal() */
  1321.     char **pword;        /* pointer to word after ordinal */
  1322. #endif
  1323. {
  1324.     int wkd, mon, mm, dd, len, (*pfcn)(), doit;
  1325.     int val_first, val_last, val_incr, mon_first, mon_last;
  1326.     DATE *pdate, date;
  1327.  
  1328.     if ((wkd = get_weekday(*pword, TRUE)) == NOT_WEEKDAY ||    /* weekday */
  1329.         *++pword == NULL ||                    /* any word */
  1330.         (mon = get_month(*++pword, FALSE, TRUE)) == NOT_MONTH) /* month */
  1331.         return PARSE_INVLINE;
  1332.  
  1333.     /* set up loop boundaries for month loop */
  1334.     mon_first = mon == ALL_MONTHS || mon == ENTIRE_YEAR ? JAN : mon;
  1335.     mon_last  = mon == ALL_MONTHS || mon == ENTIRE_YEAR ? DEC : mon;
  1336.  
  1337.     pdate = dates;            /* start of date array */
  1338.  
  1339.     /* special case of "all|odd|even <wildcard> in <month>|all|year" */
  1340.  
  1341.     if ((ord == ORD_ALL || ord == ORD_EVEN || ord == ORD_ODD) &&
  1342.         IS_WILD(wkd)) {
  1343.         pfcn = pdatefcn[wkd - WILD_FIRST];
  1344.         doit = ord != ORD_EVEN;
  1345.         for (mm = mon_first; mm <= mon_last; mm++) {
  1346.             len = LENGTH_OF(mm, curr_year);
  1347.             if (mon != ENTIRE_YEAR)
  1348.                 doit = ord != ORD_EVEN;
  1349.             for (dd = 1; dd <= len; dd++)
  1350.                 if ((*pfcn)(mm, dd, curr_year)) {
  1351.                     if (doit)
  1352.                         ADD_DATE(mm, dd, curr_year);
  1353.                     if (ord != ORD_ALL)
  1354.                         doit = ! doit;
  1355.                 }
  1356.         }
  1357.     }
  1358.  
  1359.     /* special case of "odd|even <weekday> in year" */
  1360.  
  1361.     else if ((ord == ORD_EVEN || ord == ORD_ODD) && mon == ENTIRE_YEAR) {
  1362.         date.mm = JAN;            /* starting date */
  1363.         date.dd = calc_day(ord == ORD_EVEN ? 2 : 1, wkd, JAN);
  1364.         date.yy = curr_year;
  1365.         do {                /* alternates throughout year */
  1366.             ADD_DATE(date.mm, date.dd, date.yy);
  1367.             date.dd += 14;
  1368.             normalize(&date);
  1369.         } while (date.yy == curr_year);
  1370.     }
  1371.  
  1372.     /* special case of "<ordinal>|last <weekday>|<wildcard> in year" */
  1373.  
  1374.     else if ((ord == ORD_NEGNUM || ord == ORD_POSNUM) &&
  1375.              mon == ENTIRE_YEAR) {
  1376.         if (calc_year_day(val, wkd, &date))
  1377.             ADD_DATE(date.mm, date.dd, date.yy);
  1378.     }
  1379.  
  1380.     /* all other combinations of ordinal and day */
  1381.  
  1382.     else {
  1383.         /* set up loop boundaries for "wildcard" ordinals */
  1384.  
  1385.         val_first = ord == ORD_ALL || ord == ORD_ODD ? 1 :
  1386.                 ord == ORD_EVEN ? 2 : val;
  1387.         val_last  = ord == ORD_ALL || ord == ORD_ODD ? 5 :
  1388.                 ord == ORD_EVEN ? 4 : val;
  1389.         val_incr  = ord == ORD_ODD || ord == ORD_EVEN ? 2 : 1;
  1390.  
  1391.         for (mm = mon_first; mm <= mon_last; mm++)
  1392.             for (val = val_first; val <= val_last; val += val_incr)
  1393.                 if ((dd = calc_day(val, wkd, mm)) != 0)
  1394.                     ADD_DATE(mm, dd, curr_year);
  1395.     }
  1396.  
  1397.     TERM_DATES;        /* terminate array with null entry */
  1398.     return PARSE_OK;
  1399. }
  1400.  
  1401.  
  1402. /*
  1403.  * parse_rel - parse a relative date spec (e.g. "Friday after fourth Thursday
  1404.  * in November", "2nd Saturday after first Friday in all"; return PARSE_OK if
  1405.  * line syntax valid, PARSE_INVLINE if not.  Transform all dates that match
  1406.  * the base date to the appropriate day, month, and year.
  1407.  *
  1408.  * This calls parse_date() recursively in order to handle cases such as
  1409.  * "Friday after Tuesday before last day in all".
  1410.  */
  1411. int
  1412. #ifdef PROTOS
  1413. parse_rel(int val,        /* valid (positive) ordinal value */
  1414.       int wkd,        /* valid weekday code - from get_weekday() */
  1415.       char **pword,        /* pointer to word after weekday */
  1416.       int *ptype,        /* return text type (holiday/non-holiday) */
  1417.       char ***pptext)    /* return pointer to first word of text */
  1418. #else
  1419. parse_rel(val, wkd, pword, ptype, pptext)
  1420.     int val;        /* valid (positive) ordinal value */
  1421.     int wkd;        /* valid weekday code - from get_weekday() */
  1422.     char **pword;        /* pointer to word after weekday */
  1423.     int *ptype;        /* return text type (holiday/non-holiday) */
  1424.     char ***pptext;        /* return pointer to first word of text */
  1425. #endif
  1426. {
  1427.     int prep, n, rtn, base_wkd, incr, (*pfcn)();
  1428.     DATE *pd;
  1429.  
  1430.     /* we have the weekday - now look for the preposition */
  1431.     if ((prep = get_prep(*pword++)) == PR_OTHER)
  1432.         return PARSE_INVLINE;
  1433.  
  1434.     /* get the base date */
  1435.     if ((rtn = parse_date(pword, ptype, pptext)) != PARSE_OK)
  1436.         return rtn;
  1437.  
  1438.     /* transform date array in place - note that the relative date may
  1439.        not be in the same month or even year */
  1440.  
  1441.     if (IS_WILD(wkd)) {        /* wildcard for weekday name? */
  1442.         pfcn = pdatefcn[wkd - WILD_FIRST];
  1443.         incr = prep == PR_BEFORE || prep == PR_ON_BEFORE ? -1 : 1;
  1444.  
  1445.         for (pd = dates; pd->mm; pd++) {
  1446.             /* search for nearest matching date */
  1447.  
  1448.             if (prep == PR_BEFORE || prep == PR_AFTER) {
  1449.                 pd->dd += incr;
  1450.                 normalize(pd);
  1451.             }
  1452.             /* If NEAREST_INCR (cf. pcaldefs.h) is 1, Pcal will
  1453.              * disambiguate "nearest" in favor of the later date;
  1454.              * if -1, in favor of the earlier.  "incr" will take
  1455.              * the values 1, -2, 3, -4, ... or -1, 2, -3, 4 ...
  1456.              * respectively.
  1457.              */
  1458.             if (prep == PR_NEAREST) {
  1459.                 val = 1;    /* ordinals meaningless here */
  1460.                 incr = NEAREST_INCR;
  1461.                 }
  1462.             n = val;
  1463.             while (!((*pfcn)(pd->mm, pd->dd, pd->yy) && --n == 0)) {
  1464.                 pd->dd += incr;
  1465.                 normalize(pd);
  1466.                 if (prep == PR_NEAREST)
  1467.                     incr -= (incr > 0) ? (2 * incr + 1) :
  1468.                                  (2 * incr - 1);
  1469.             }
  1470.         }
  1471.  
  1472.     } else  {            /* explicit weekday name */
  1473.         for (pd = dates; pd->mm; pd++) {
  1474.             /* calculate nearest matching date */
  1475.  
  1476.             base_wkd = calc_weekday(pd->mm, pd->dd, pd->yy);
  1477.             switch (prep) {
  1478.             case PR_BEFORE:
  1479.             case PR_ON_BEFORE:
  1480.                 if (prep == PR_BEFORE || wkd != base_wkd)
  1481.                     pd->dd -= 7 - (wkd - base_wkd + 7) % 7;
  1482.                 pd->dd -= 7 * (val - 1);
  1483.                 break;
  1484.  
  1485.             case PR_AFTER:
  1486.             case PR_ON_AFTER:
  1487.                 if (prep == PR_AFTER || wkd != base_wkd)
  1488.                     pd->dd += (wkd - base_wkd + 6) % 7 + 1;
  1489.                 pd->dd += 7 * (val - 1);
  1490.                 break;
  1491.  
  1492.             case PR_NEAREST:
  1493.                 /* use closer of previous and next */
  1494.                 val = wkd - base_wkd;
  1495.                 pd->dd += (val > 3) ? (val - 7) :
  1496.                       (val < -3) ? (val + 7) : val;
  1497.                 break;
  1498.  
  1499.             default:
  1500.                 return PARSE_INVLINE;
  1501.                 break;
  1502.             }
  1503.  
  1504.             normalize(pd);    /* adjust for month/year crossing */
  1505.         }
  1506.     }
  1507.  
  1508.     return PARSE_OK;
  1509. }
  1510.  
  1511.  
  1512. /*
  1513.  * parse_date - parse a date specification in any of its myriad forms; upon
  1514.  * return, array dates[] will contain a list of all the dates that matched,
  1515.  * terminated by a null entry.  Also fill in the date type (holiday/non-
  1516.  * holiday) code and the pointer to the first word of text.
  1517.  */
  1518. int
  1519. #ifdef PROTOS
  1520. parse_date(char **pword,    /* first word to parse */
  1521.        int *ptype,        /* return date type (holiday/non-holiday) */
  1522.        char ***pptext)    /* return pointer to first word of text */
  1523. #else
  1524. parse_date(pword, ptype, pptext)
  1525.     char **pword;        /* first word to parse */
  1526.     int *ptype;        /* return date type (holiday/non-holiday) */
  1527.     char ***pptext;        /* return pointer to first word of text */
  1528. #endif
  1529. {
  1530.     int mm, dd, yy;
  1531.     int token, n, v, ord, val, wkd, rtn;
  1532.     DATE *pdate;
  1533.     char *cp;
  1534.  
  1535.     pdate = dates;
  1536.  
  1537.     switch (token = date_type(*pword, &n, &v)) {
  1538.  
  1539.     case DT_MONTH:        /* <month> dd */
  1540.         if (date_style != USA_DATES)
  1541.             return PARSE_INVLINE;
  1542.  
  1543.         if ((cp = *++pword) == NULL)
  1544.             return PARSE_INVLINE;
  1545.  
  1546.         ADD_DATE(n, atoi(cp), curr_year);
  1547.         TERM_DATES;
  1548.  
  1549.         break;
  1550.  
  1551.     case DT_DATE:        /* mm/dd{/yy} | dd/mm{/yy} */
  1552.         n = split_date(*pword,
  1553.                    date_style == USA_DATES ? &mm : &dd,
  1554.                    date_style == USA_DATES ? &dd : &mm,
  1555.                    &yy);
  1556.  
  1557.         if (n > 2) {            /* year present? */
  1558.             if (yy < 100)
  1559.                 yy += CENTURY;
  1560.             curr_year = yy;        /* reset current year */
  1561.         }
  1562.  
  1563.         ADD_DATE(mm, dd, curr_year);
  1564.         TERM_DATES;
  1565.  
  1566.         break;
  1567.  
  1568.     case DT_EURDATE:    /* dd [ <month> | "all" ] */
  1569.         if (date_style != EUR_DATES)
  1570.             return PARSE_INVLINE;
  1571.  
  1572.         dd = atoi(*pword);
  1573.  
  1574.         if (get_keywd(*++pword) == DT_ALL) {
  1575.             for (mm = JAN; mm <= DEC; mm++)        /* wildcard */
  1576.                 ADD_DATE(mm, dd, curr_year);
  1577.         }
  1578.         else {                        /* one month */
  1579.             if ((mm = get_month(*pword, TRUE, FALSE)) == NOT_MONTH)
  1580.                 return PARSE_INVLINE;
  1581.  
  1582.             ADD_DATE(mm, dd, curr_year);
  1583.         }
  1584.  
  1585.         TERM_DATES;
  1586.         break;
  1587.  
  1588.     case DT_ALL:        /* "all" <weekday> "in" [ <month> | "all" ] */
  1589.                 /* or "all" <day>" */
  1590.  
  1591.         if ((cp = *(pword+1)) && (*(cp += strspn(cp, DIGITS)) == '\0' ||
  1592.             *cp == '*')) {
  1593.             dd = atoi(*++pword);        /* "all" <day> */
  1594.             for (mm = JAN; mm <= DEC; mm++)
  1595.                 ADD_DATE(mm, dd, curr_year);
  1596.             TERM_DATES;
  1597.             break;        /* leave switch */
  1598.         }
  1599.  
  1600.         n = ORD_ALL;    /* "all" <weekday> ... */
  1601.         v = 0;
  1602.          /* fall through */
  1603.  
  1604.     case DT_ORDINAL:    /* <ordinal> <weekday> in [ <month> | "all" ] */
  1605.                  /* or <ordinal> <weekday> <prep> <date> */
  1606.         ord = n;
  1607.         val = v;
  1608.         /* disambiguate above cases based on preposition */
  1609.         if (ord == ORD_POSNUM && pword[1] &&
  1610.             (get_prep(pword[2]) != PR_OTHER)) {
  1611.             if ((wkd = get_weekday(pword[1], TRUE)) == NOT_WEEKDAY)
  1612.                 return PARSE_INVLINE;
  1613.             return parse_rel(val, wkd, pword += 2, ptype, pptext);
  1614.         }
  1615.         if ((rtn = parse_ord(ord, val, pword + 1)) != PARSE_OK)
  1616.             return rtn;
  1617.  
  1618.         pword += 3;        /* last word of date */
  1619.         break;
  1620.  
  1621.     case DT_WEEKDAY:    /* <weekday> <prep> <date> */
  1622.         wkd = n;
  1623.         /* parse_rel() calls parse_date() recursively */
  1624.         return parse_rel(1, wkd, ++pword, ptype, pptext);
  1625.         break;
  1626.  
  1627.     case DT_HOLIDAY:    /* predefined holiday */
  1628.         /*
  1629.          * predefined holidays will either have a redefinition string
  1630.          * or a dispatch function - never both (cf. pcallang.h)
  1631.          */
  1632.         if (holidays[n].pfcn == NULL) {
  1633.             char redef[STRSIZ], *rwords[20], **pdum;
  1634.             int rtn, idum;
  1635.  
  1636.             /* tokenize local copy of redefinition string */
  1637.             strcpy(redef, holidays[n].def);
  1638.             (void) loadwords(rwords, redef);
  1639.  
  1640.             /* call parse_date() recursively to parse the
  1641.              * redefinition - if OK, drop through to fill in
  1642.              * real ptype and pptext from original string (this
  1643.              * works basically because the holiday is always
  1644.              * the last token before the text)
  1645.              */
  1646.             if ((rtn = parse_date(rwords, &idum, &pdum)) != PARSE_OK)
  1647.                 return rtn;
  1648.         } else {
  1649.             /* holiday has a dispatch function - use it */
  1650.             pdate += (*holidays[n].pfcn)(pdate);
  1651.             TERM_DATES;
  1652.         }
  1653.         break;
  1654.  
  1655.     default:
  1656.         return PARSE_INVLINE;
  1657.         break;
  1658.     }
  1659.  
  1660.     /* at this point, pword points to the last component of the date;
  1661.      * fill in type code and pointer to following word (start of text)
  1662.      */
  1663.     *ptype = LASTCHAR(*pword) == '*' ? HOLIDAY_TEXT : DAY_TEXT;
  1664.     *pptext = ++pword;
  1665.  
  1666.     return PARSE_OK;
  1667. }
  1668.  
  1669.  
  1670. /*
  1671.  * parse - parse non-preprocessor lines in date file
  1672.  *
  1673.  * This routine parses "year", "opt", "note", and date entries in the date
  1674.  * file.  It calls parse_date() to parse date entries (and enter the date(s)
  1675.  * matched in global array "dates"), and then calls enter_day_info() to
  1676.  * enter each date found (and its associated text) in the date file.
  1677.  *
  1678.  * N.B.: "inc" and other cpp-like lines are handled in read_datefile().
  1679.  *
  1680.  */
  1681. int
  1682. #ifdef PROTOS
  1683. parse(char **pword,        /* pointer to first word to parse */
  1684.       char *filename)        /* name of file (for error messages) */
  1685. #else
  1686. parse(pword, filename)
  1687.     char **pword;        /* pointer to first word to parse */
  1688.     char *filename;        /* name of file (for error messages) */
  1689. #endif
  1690. {
  1691.     register char *cp;
  1692.     char **ptext;
  1693.     int mm, yy;
  1694.     int text_type, n, v, rtn, match, valid;
  1695.     int token;
  1696.     DATE *pd;
  1697.  
  1698.     /*
  1699.      * Get first field and call date_type() to decode it
  1700.          */
  1701.     cp = *pword;
  1702.  
  1703.     switch (token = date_type(cp, &n, &v)) {
  1704.  
  1705.     case DT_YEAR:
  1706.         if ((cp = *++pword) != NULL && (yy = atoi(cp)) > 0) {
  1707.             if (yy < 100)
  1708.                 yy += CENTURY;
  1709.             curr_year = yy;
  1710.             return PARSE_OK;
  1711.         }
  1712.         return PARSE_INVLINE;    /* year missing or non-numeric */
  1713.         break;
  1714.  
  1715.     case DT_OPT:
  1716.          if (!get_args(pword, P_OPT, filename, FALSE)) {
  1717.             usage(stderr, FALSE);
  1718.             exit(EXIT_FAILURE);
  1719.         }
  1720.         return PARSE_OK;
  1721.         break;
  1722.  
  1723.     case DT_NOTE:
  1724.         /* look for optional "/<n>" following keyword */
  1725.         n = (cp = strrchr(cp, '/')) ? atoi(++cp) : 0;
  1726.  
  1727.         if ((mm = get_month(*++pword, TRUE, TRUE)) == NOT_MONTH)
  1728.             return PARSE_INVLINE;
  1729.  
  1730.         if (mm == ALL_MONTHS || mm == ENTIRE_YEAR) {   /* "note all"? */
  1731.             valid = FALSE;    /* is at least one note box valid? */
  1732.             for (mm = JAN; mm <= DEC; mm++)
  1733.                 valid |= enter_day_info(mm,
  1734.                         note_day(mm, n, curr_year),
  1735.                         curr_year, NOTE_TEXT,
  1736.                         pword+1) == PARSE_OK;
  1737.             return valid ? PARSE_OK : PARSE_NOMATCH;
  1738.         }
  1739.  
  1740.         return enter_day_info(mm, note_day(mm, n, curr_year),
  1741.                 curr_year, NOTE_TEXT, pword+1);
  1742.         break;
  1743.  
  1744.     case DT_OTHER:        /* unrecognized token */
  1745.         return PARSE_INVLINE;
  1746.         break;
  1747.  
  1748.     /* assume anything else is a date */
  1749.  
  1750.     default:
  1751.         if ((rtn = parse_date(pword, &text_type, &ptext)) == PARSE_OK) {
  1752.             match = FALSE;    /* is at least one date valid? */
  1753.             for (pd = dates; pd->mm; pd++)
  1754.                 match |= enter_day_info(pd->mm, pd->dd, pd->yy,
  1755.                            text_type, ptext) == PARSE_OK;
  1756.             rtn = match ? PARSE_OK : PARSE_NOMATCH;
  1757.         }
  1758.         return rtn;
  1759.         break;
  1760.  
  1761.     }
  1762. }
  1763.